package com.googlecode.shapeline.data.oswf;

import java.io.*;
import java.util.*;
import org.xml.sax.SAXException;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.opensymphony.module.propertyset.PropertySet;
import com.opensymphony.workflow.loader.*;
import com.opensymphony.workflow.FactoryException;
import com.opensymphony.workflow.FunctionProvider;
//import com.googlecode.shapeline.web.context.support.ApplicationContextUtils;


public class IBatisWorkflowFactory extends XMLWorkflowFactory implements FunctionProvider {
	private final static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(IBatisWorkflowFactory.class.getName());

	protected SqlMapClient sqlMapClient;
    protected Map workflows;
    protected boolean reload;
    
    public void setSqlMapClient(SqlMapClient sqlMapClient) {
    	this.sqlMapClient = sqlMapClient;
    }
    
    public SqlMapClient getSqlMapClient() {
    	return sqlMapClient;
    	//return (SqlMapClient)ApplicationContextUtils.getInstance().getApplicationContext().getBean("sqlMapClient");
    }
    
    public WorkflowDescriptor getWorkflow(String name, boolean validate) throws FactoryException {
        WfConfig c = (WfConfig) workflows.get(name);

        if (c == null) {
            throw new RuntimeException("Unknown workflow name \"" + name + "\"");
        }

        if (log.isDebugEnabled()) {
            log.debug("getWorkflow " + name + " descriptor=..to long, no log...");
        }

        if (c.descriptor != null) {
            if (reload) {
                //@todo check timestamp
                try {
                    c.descriptor = load(c.wfName, validate);
                } catch (FactoryException e) {
                    throw e;
                } catch (Exception e) {
                    throw new FactoryException("Error reloading workflow", e);
                }
            }
        } else {
            try {
                c.descriptor = load(c.wfName, validate);
            } catch (FactoryException e) {
                throw e;
            } catch (Exception e) {
                throw new FactoryException("Error loading workflow", e);
            }
        }

        return c.descriptor;
    }

    public String[] getWorkflowNames() {
        int i = 0;
        String[] res = new String[workflows.keySet().size()];
        Iterator it = workflows.keySet().iterator();

        while (it.hasNext()) {
            res[i++] = (String) it.next();
        }

        return res;
    }

    public void execute(Map transientVars, Map args, PropertySet ps) {
        String name = (String) args.get("name");
        WorkflowDescriptor wfds = (WorkflowDescriptor) transientVars.get("descriptor");

        try {
            saveWorkflow(name, wfds, false);
        } catch (Exception e) {
        }
    }

    public void initDone() throws FactoryException {
        try {
            init();
            reload = getProperties().getProperty("reload", "false").equalsIgnoreCase("true");

            List list = getSqlMapClient().queryForList("selectWorkflowConfig", null);
            log.debug("select wf defs from db, count: " + list.size());
            for (int i = 0; i < list.size(); i ++) {
            	WorkflowConfigData data = (WorkflowConfigData)list.get(i);
            	String name = data.getName();
            	log.debug("get wf def, name: " + name);
            	WfConfig config = new WfConfig(name);
            	workflows.put(name, config);
            }
        } catch (Exception e) {
            throw new FactoryException("Could not read workflow names from datasource", e);
        }
    }

    public byte[] read(String workflowname) throws FactoryException {
        byte[] wf = new byte[0];
        try {
        	WorkflowConfigData data = (WorkflowConfigData)getSqlMapClient().queryForObject("selectWorkflowConfig", workflowname);
        	wf = data.getDescriptor().getBytes();
        	log.debug("get def config from database, name: " + workflowname + " bytes: " + wf.length);
        }
        catch (Exception e) {
        	throw new FactoryException("Unable to read workflow: " + e.toString(), e);
        }

        return wf;
    }

    public boolean removeWorkflow(String name) throws FactoryException {
        boolean removed = false;

        try {
        	int rows = this.getSqlMapClient().delete("deleteWorkflowConfig", name);

            if (rows == 1) {
                removed = true;
                workflows.remove(name);
            }
        } catch (Exception e) {
            throw new FactoryException("Unable to remove workflow: " + e.toString(), e);
        }

        return removed;
    }

    public boolean saveWorkflow(String name, WorkflowDescriptor descriptor, boolean replace) throws FactoryException {
        WfConfig c = (WfConfig) workflows.get(name);

        if ((c != null) && !replace) {
            return false;
        }

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(bout);

        PrintWriter writer = new PrintWriter(out);
        writer.println(WorkflowDescriptor.XML_HEADER);
        writer.println(WorkflowDescriptor.DOCTYPE_DECL);
        descriptor.writeXML(writer, 0);
        writer.flush();
        writer.close();

        //@todo is a backup necessary?
        try {
            return write(name, bout.toByteArray());
        } catch (Exception e) {
            throw new FactoryException("Unable to save workflow: " + e.toString(), e);
        } finally {
            WfConfig config = new WfConfig(name);
            workflows.put(name, config);
        }
    }

    public boolean write(String workflowname, byte[] wf) throws FactoryException {
        boolean written = false;
        try {
        	WorkflowConfigData data = new WorkflowConfigData();
        	data.setName(workflowname);
        	data.setDescriptor(new String(wf, "utf-8"));

            if (exists(workflowname)) {
            	getSqlMapClient().update("updateWorkflowConfig", data);
            } else {
            	getSqlMapClient().update("insertWorkflowConfig", data);
            }
            written = true;
        } catch (Exception e) {
        	throw new FactoryException("Unable to save workflow: " + e.toString(), e);
        }
        return written;
    }

    private boolean exists(String workflowname) {
        boolean exists = false;

        try {
        	WorkflowConfigData data = (WorkflowConfigData)getSqlMapClient().queryForObject("selectWorkflowConfig", workflowname);
        	
            if (data != null) {
                exists = true;
            }
        } catch (Exception e) {
            log.fatal("Could not check if [" + workflowname + "] exists", e);
        }
        return exists;
    }

    private void init() {
        workflows = new HashMap();
    }

    private WorkflowDescriptor load(final String wfName, boolean validate) throws IOException, SAXException, FactoryException {
        byte[] wf;

        try {
            wf = read(wfName);
        } catch (Exception e) {
            throw new FactoryException("Error loading workflow:" + e, e);
        }

        ByteArrayInputStream is = new ByteArrayInputStream(wf);

        return WorkflowLoader.load(is, validate);
    }

	public void setReload(boolean reload) {
		this.reload = reload;
	}

   //~ Inner Classes //////////////////////////////////////////////////////////
    class WfConfig {
        String wfName;
        WorkflowDescriptor descriptor;
        long lastModified;

        public WfConfig(String name) {
            wfName = name;
        }
    }
	
}